/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.mybatis.builder;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.jianggujin.dbfly.mybatis.JConfiguration;
import com.jianggujin.dbfly.mybatis.JXMLMapperBuilder;
import com.jianggujin.dbfly.mybatis.util.JMybatisUtils;
import com.jianggujin.dbfly.util.JExceptionUtils;
import com.jianggujin.dbfly.util.JLinkedEntry;
import com.jianggujin.dbfly.util.JLinkedList;
import com.jianggujin.dbfly.util.JObjectRef;
import com.jianggujin.dbfly.util.JStringUtils;

/**
 * {@link MappedStatement}构造器默认实现
 * 
 * @author jianggujin
 *
 */
public class JDefaultMappedStatementBuilder implements JMappedStatementBuilder {
    private final static Log logger = LogFactory.getLog(JDefaultMappedStatementBuilder.class);
    /**
     * 动态方法名，自动映射同名方法
     */
    public static final String DYNAMIC_METHOD = "dynamicMethod";
    private final JLinkedList<String, JParser> parsers = new JLinkedList<>();

    public JDefaultMappedStatementBuilder() {
        this.parsers.addLast("mapperMethodParser", new JMapperMethodParser());
        this.parsers.addLast("methodNameParser", new JMethodNameParser());
    }

    @Override
    public void parse(JConfiguration configuration, Class<?> mapperClass) {
        synchronized (mapperClass) {
            String namespace = mapperClass.getName();
            if (configuration.isResourceLoaded(namespace)) {
                return;
            }
            Method[] methods = JMybatisUtils.filterUnParseMethods(configuration, mapperClass);
            if (methods == null || methods.length == 0) {
                return;
            }
            try {
                Document document = JMybatisUtils.newDocument();
                Element mapperElement = document.createElement("mapper");
                mapperElement.setAttribute("namespace", namespace);

                for (Method method : methods) {
                    this.parseMethod(configuration, mapperClass, method, document, mapperElement);
                }
                document.appendChild(mapperElement);

                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                JMybatisUtils.transform(document, stream);
                if (logger.isDebugEnabled()) {
                    logger.debug(stream.toString());
                }
                JXMLMapperBuilder builder = new JXMLMapperBuilder(new ByteArrayInputStream(stream.toByteArray()),
                        configuration, JStringUtils.format("{}.dbfly", mapperClass.getName()),
                        configuration.getSqlFragments(), namespace);
                builder.parse();
            } catch (Exception e) {
                throw JExceptionUtils.handleException(e);
            }
        }
    }

    /**
     * 解析指定方法
     * 
     * @param configuration
     * @param mapperClass
     * @param method
     * @param document
     * @param mapperElement
     */
    private void parseMethod(JConfiguration configuration, Class<?> mapperClass, Method method, Document document,
            Element mapperElement) {
        if (this.parsers.isEmpty()) {
            return;
        }
        JObjectRef<Object> ref = new JObjectRef<>();
        for (JLinkedEntry<String, JParser> parser : this.parsers) {
            if (parser.getValue().support(configuration, mapperClass, method, ref)) {
                parser.getValue().parse(configuration, mapperClass, method, document, mapperElement, ref);
                return;
            }
            ref.setValue(null);
        }
    }

    @Override
    public boolean support(JConfiguration configuration, Class<?> mapperClass) {
        return true;
    }

    public JLinkedList<String, JParser> getParsers() {
        return parsers;
    }

    /**
     * 解析器
     * 
     * @author jianggujin
     *
     */
    public static interface JParser {
        boolean support(JConfiguration configuration, Class<?> mapperClass, Method method, JObjectRef<Object> ref);

        void parse(JConfiguration configuration, Class<?> mapperClass, Method method, Document document,
                Element mapperElement, Object ref);
    }
}
